// Anti-fall of bike filterscript by dreft-k.

#tryinclude <a_samp>
#tryinclude <a_players>
#tryinclude <a_vehicles>

#include <string>

#if !defined _samp_included
	#error Not SA-MP or unknown version detected!
	#endinput
#endif

//#define __samp03

/********** Start user defines ***********/
#define NUM_PLAYERS 20 // like MAX_PLAYERS
#define ANTI_FALL_ENABLED 0 // 0 to enable, -1 and less to disable
#define NO_TIMERS true // disable all timers
#define RETURN_ON_BIKE_AFTER 0 // milliseconds, only works if NO_TIMERS is false
#define STOP_BIKE_METHOD 6 	// 0 for no stop, 1 - to stop excluding rotational speed, 2 - full stop (visible lag),
							// 3 - correct speed to move forward (beta), 4 - the same as 3 plus avoid rotational speed,
							// 5 - correct angle to move forward, 6 - the same as 5 plus avoid rotational speed.
#define MIN_TELEPORT_RANGE 30 // is player teleports, we willn't put him back
#define ALT_VEHICLEEXIT true // use F/ENTER key to clear player's vehicle
/********** End of user defines **********/

#define VEHICLES_LIMIT (2048) // you can increase it without any effect
//#define PRESSED(%0) (((newkeys & (%0)) == (%0)) && ((oldkeys & (%0)) != (%0)))

//#define IsPlayerDriver(%0) (GetPlayerState(%0)==PLAYER_STATE_DRIVER)
#define af_IsPlayerDriver(%0) ((playerdriving[%0]<VEHICLES_LIMIT)?true:false) // is player driver?
#define af_playerdriving(%0) ((playerdriving[%0]>(VEHICLES_LIMIT))?(playerdriving[%0]-\
	(VEHICLES_LIMIT)):playerdriving[%0]) // which vehicle player driving now?
#define af_IsValidPlayerID(%0) ((((%0)>=0)&&((%0)<NUM_PLAYERS)&&((%0)!=INVALID_PLAYER_ID)&&(!IsPlayerNPC(%0)))?true:false)

#assert NUM_PLAYERS <= MAX_PLAYERS // must be less or equal than max players
#assert NUM_PLAYERS > 0 // players cant be negative or be zero
#assert RETURN_ON_BIKE_AFTER >= 0 // time cant be negative
#assert ANTI_FALL_ENABLED < 1 // cant assign player to not valid vehicles
#assert MIN_TELEPORT_RANGE >= 0 // range cant be negative
#assert VEHICLES_LIMIT > MAX_VEHICLES // vehicles limit must be more than max vehicles
#assert VEHICLES_LIMIT < (0x7FFFFFFE-(MAX_VEHICLES)) // for no overflowing
#assert STOP_BIKE_METHOD >= 0 && STOP_BIKE_METHOD <= 6 // can't do not valid methods

#if !defined __samp03 && STOP_BIKE_METHOD !=0 && STOP_BIKE_METHOD != 2
#error Only methods 0 and 2 are available in non-0.3 version
#endif

static playerdriving[NUM_PLAYERS]; // vehicleid or (vehicleid+VEHICLES_LIMIT) which player driving now
forward af_PutPlayerBackInVehicle(playerid); // to return player to his vehicle
forward af_ClearPlayerVehicle(playerid);

public OnFilterScriptInit()
{
	//print("\t\t\t\t\t Anti-fall of bike by dreft.");
	return 1;
}

public OnPlayerConnect(playerid)
{
	if(af_IsValidPlayerID(playerid))
		playerdriving[playerid] = ANTI_FALL_ENABLED; // clearing player vehicle when new player connected
	return 1;
}

#if defined ALT_VEHICLEEXIT && ALT_VEHICLEEXIT == true
public OnPlayerKeyStateChange(playerid, newkeys, oldkeys)
{
	if(playerdriving[playerid] <= 0) return 1;
	if((newkeys & KEY_SECONDARY_ATTACK) && playerdriving[playerid]) // if player jumped off the vehicle
	{
		//printf("%d is jumped off", playerid);
		playerdriving[playerid] = ANTI_FALL_ENABLED;
	}
	return 1;
}
#endif

public OnPlayerDeath(playerid, killerid, reason)
{
	#pragma unused killerid
	#pragma unused reason
	if(playerdriving[playerid] > 0 && af_IsValidPlayerID(playerid)) // is player valid ?
		playerdriving[playerid] = ANTI_FALL_ENABLED; // clearing player vehicle on death
	return true;
}

public OnPlayerStateChange(playerid, newstate, oldstate)
{
	//printf("OnPlayerStateChange(%d, %d, %d);", playerid, newstate, oldstate);
	if(playerdriving[playerid] < 0 || !af_IsValidPlayerID(playerid)) return true;
	if(newstate == PLAYER_STATE_DRIVER)
	{
		new vid = GetVehicleModel(GetPlayerVehicleID(playerid));
		if(vid!=537&&vid!=538&&vid!=449) playerdriving[playerid] = GetPlayerVehicleID(playerid); // new vehicle assigning
		else if(playerdriving[playerid] > 0) playerdriving[playerid] = ANTI_FALL_ENABLED;
	}
	else if(newstate == PLAYER_STATE_PASSENGER)
	{
		playerdriving[playerid] = (GetPlayerVehicleID(playerid) + VEHICLES_LIMIT);
	}
	else if(newstate == 4) // doesn't work
	{ //
		playerdriving[playerid] = ANTI_FALL_ENABLED; // if player jumped off the vehicle
	}
	else if(playerdriving[playerid] && newstate == PLAYER_STATE_ONFOOT && (oldstate == PLAYER_STATE_DRIVER || //
		(oldstate == PLAYER_STATE_PASSENGER && !af_IsPlayerDriver(playerid))))
	{
		//printf("%d is fell off", playerid);
		#if defined NO_TIMERS && NO_TIMERS == true
		af_PutPlayerBackInVehicle(playerid);
		#else
		SetTimerEx("af_PutPlayerBackInVehicle", RETURN_ON_BIKE_AFTER+1, false, "i", playerid); // returning player back to his vehicle
		#endif
	}
	return true;
}

public OnVehicleDeath(vehicleid, killerid)
{
	#pragma unused killerid
	for(new i = 0; i<NUM_PLAYERS; i++)
	{
		if(af_playerdriving(i) == vehicleid) // is playerid valid ?
			playerdriving[i] = ANTI_FALL_ENABLED;
	}
	return true;
}

public OnPlayerCommandText(playerid, cmdtext[])
{
	if(!af_IsValidPlayerID(playerid)) return false;
	if(strcmp(cmdtext, "/antifall", true, 9) == 0)
	{
		if(playerdriving[playerid] == -1)
		{
			new vid = GetPlayerVehicleID(playerid);
			if(vid!=537&&vid!=538&&vid!=449) playerdriving[playerid] = vid;
			SendClientMessage(playerid, 0xAAAAAAFF, "Anti-fall of bike is now enabled!");
		} else if(playerdriving[playerid] >= 0){
			playerdriving[playerid] = -1;
			SendClientMessage(playerid, 0xAAAAAAFF, "Anti-fall of bike is now disabled!");
		} else return false; // anti-fall disabled
		return true;
	}
//	if(playerdriving[playerid]>0&&cmdtext[1]=='v'&&cmdtext[2]<=' ') // /v
//		playerdriving[playerid] = ANTI_FALL_ENABLED;
	return false;
}

public af_PutPlayerBackInVehicle(playerid)
{
	if(!af_IsValidPlayerID(playerid)) return false; // is playerid valid ?
	if(af_playerdriving(playerid) < 1 || IsPlayerInAnyVehicle(playerid) || // is player already in vehicle?
		GetPlayerSpecialAction(playerid) == SPECIAL_ACTION_EXIT_VEHICLE) return false; //  if player jumped off the vehicle or anti-fall deactivated
	//if(IsPlayerDriver(GetPlayerInVehicle(af_playerdriving(playerid)))) return false; // if carjacked?
	new Float:vpos[3];
	GetVehiclePos(af_playerdriving(playerid), vpos[0], vpos[1], vpos[2]);
	if(!IsPlayerInRangeOfPoint(playerid, MIN_TELEPORT_RANGE, vpos[0], vpos[1], vpos[2]))
		return false; // is player teleports, we willn't put him back
	#if STOP_BIKE_METHOD != 0 // stop bike
	if(af_IsPlayerDriver(playerid)) // can stop only if putting back driver
	#endif
	#if STOP_BIKE_METHOD == 1 // stop excluding rotational speed
		SetVehicleVelocity(af_playerdriving(playerid), 0.0,0.0,0.20); // vehicle stop
	#elseif STOP_BIKE_METHOD == 2 // full stop
		SetVehiclePos(af_playerdriving(playerid), vpos[0], vpos[1], vpos[2]); // setting vehicle position
	#elseif STOP_BIKE_METHOD == 3
		ClearVehicleSpeed(af_playerdriving(playerid)); // correct speed to move forward
	#elseif STOP_BIKE_METHOD == 4
		ClearVehicleSpeed(af_playerdriving(playerid)); // correct speed to move forward and avoid rotational speed
	#elseif STOP_BIKE_METHOD == 5
		ClearVehicleAngle(af_playerdriving(playerid)); // correct angle to move forward
	#elseif STOP_BIKE_METHOD == 6
		ClearVehicleAngle(af_playerdriving(playerid)); // correct angle to move forward and avoid rotational speed
	#elseif STOP_BIKE_METHOD != 0
		return false; // if non-valid method assigned
	#endif

	//printf("putting %d back in vehicle %d", playerid, af_playerdriving(playerid));
	PutPlayerInVehicle(playerid,af_playerdriving(playerid), af_IsPlayerDriver(playerid)?0:1); // putting player in his vehicle
	return true;
}

public af_ClearPlayerVehicle(playerid) { if(playerdriving[playerid] > 0) playerdriving[playerid] = ANTI_FALL_ENABLED; return; }

public OnPlayerExitVehicle(playerid, vehicleid)
{
	if(af_IsValidPlayerID(playerid) && af_playerdriving(playerid) == vehicleid)
		playerdriving[playerid] = ANTI_FALL_ENABLED; // clearing player vehicle on player exiting vehicle
	return true;
}

public OnVehicleSpawn(vehicleid)
{
	for(new i=0; i<NUM_PLAYERS; i++)
	{
		if(IsPlayerConnected(i) && af_playerdriving(i) == vehicleid)
			playerdriving[i] = ANTI_FALL_ENABLED; // clearing player vehicle on vehicle respawn
	}
	return true;
}

#if STOP_BIKE_METHOD == 3 || STOP_BIKE_METHOD == 4
stock ClearVehicleSpeed(vehicleid)
{
	new Float:pvel[3], Float:pspeed, Float:pang;
	GetVehicleVelocity(vehicleid, pvel[0], pvel[1], pvel[2]);
	GetVehicleZAngle(vehicleid, pang);
	pspeed = floatsqroot(floatpower(pvel[0],2) + floatpower(pvel[1],2));// + floatpower(pvel[2],2) );
	pvel[0] = (pspeed * floatsin(-pang, degrees));
	pvel[1] = (pspeed * floatcos(-pang, degrees));
	#if STOP_BIKE_METHOD == 4
	/*
	new Float:ppos[3];
	GetVehiclePos(vehicleid, ppos[0], ppos[1], ppos[2]);
	SetVehiclePos(vehicleid, ppos[0], ppos[1], ppos[2]); // stoppin vehicle
	*/
	SetVehicleAngularVelocity(vehicleid, 0.0, 0.0, 0.0);
	#endif
	SetVehicleVelocity(vehicleid,pvel[0],pvel[1],pvel[2]); // set vehicle to move directionally forward
	return true;
}
#endif

#if STOP_BIKE_METHOD == 5 || STOP_BIKE_METHOD == 6
#define IsNumberNearby(%0,%1,%2) ((((%0)>=(%2))&&(((%0)-(%1))<=(%2)))||(((%0)<=(%2))&&(((%0)+(%1))>=(%2))))
stock ClearVehicleAngle(vehicleid)
{
	new Float:pvel[4];
	GetVehicleVelocity(vehicleid,pvel[0],pvel[1],pvel[2]);
	#if STOP_BIKE_METHOD == 6
	/*
	new Float:ppos[3];
	GetVehiclePos(vehicleid, ppos[0], ppos[1], ppos[2]);
	SetVehiclePos(vehicleid, ppos[0], ppos[1], ppos[2]);
	*/
	SetVehicleAngularVelocity(vehicleid, 0.0, 0.0, 0.0);
	#endif
	if(IsNumberNearby(pvel[0],0.2,0)&&IsNumberNearby(pvel[1],0.2,0))
		GetVehicleZAngle(vehicleid, pvel[3]);
	else
		pvel[3] = (-atan2(pvel[0], pvel[1]));
	SetVehicleZAngle(vehicleid, pvel[3]);
	#if STOP_BIKE_METHOD == 6
	SetVehicleVelocity(vehicleid,pvel[0],pvel[1],pvel[2]);
	#endif
	return true;
}
#endif

/*
stock GetPlayerInVehicle(vehicleid)
{
	for(new i=0; i<NUM_PLAYERS; i++)
	if(IsPlayerConnected(i)&&IsPlayerInVehicle(i, vehicleid)) return i;
	return INVALID_PLAYER_ID;
}
*/
